let split chr str =
  let len = String.length str - 1
  and res = ref []
  and accu = ref "" in
    res := [];
    accu := "";
    for i = 0 to len do
      if (str.[i] = chr) && (!accu <> "") then
        begin
          res := !accu :: !res;
          accu := "";
        end
      else
        accu := !accu ^ (String.make 1 str.[i])
    done;
    Array.of_list (!accu :: !res)

let xml =
   Glade.create ~file:"serious.glade" ()

let id = ref None

(*Quit the application*)
let destroy () = GMain.Main.quit ()

let st_bar = new GMisc.statusbar
  (GtkMisc.Statusbar.cast(Glade.get_widget xml "statusbar1"))

let ctxt = st_bar#new_context ~name:"Global"
let add_status s =
  ctxt#pop();
  let _  = ctxt#push s in
  ()

let about = new GMenu.menu_item
   (GtkMenu.MenuItem.cast(Glade.get_widget xml "about"))

let aboutlol () =
let winAbout = GWindow.about_dialog
	~authors:["Hugo Brac"; "Vincent Lefebvre"; "Antoine Jackson"; "FLorent Paillard"]
	~comments:"A 3D Mapping software.\n\n
Developers: Four Epita’s students\n\n
Why: Epita’s second year project\n\n
Programing Language: Objective caml.\n\n
Libraries used: SDL, GTK2, OPEN GL, GLADE.\n\n
Learn more about Epita at http://en.wikipedia.org/wiki/EPITA\n\n
Learn more about Caml language at http://caml.inria.fr/\n\n
Contact: bracde_h@epita.fr "
	~name:"Epimapping 3D"
	~website:"http://code.google.com/p/epimapping3d/"
	~show:true () in
  let _ = winAbout#connect#close ~callback:(fun () -> winAbout#destroy ()) in
()

(************************ 3D ENGINE , DO NOT TOUCH!!!! ***************************)
let event_boxgl = new GBin.event_box
  (GtkBin.EventBox.cast(Glade.get_widget xml "eventbox1"))

let gl_area = GlGtk.area [ `RGBA;
                           `DOUBLEBUFFER;
                           `DEPTH_SIZE 1;
                           `USE_GL]
                ~packing:event_boxgl#add 
                ~show:true 
                ~height:600
                ~width:800 ()

let glEngine = new E3d.glEngine gl_area

let launch3DEngine () =
  if Sys.file_exists "map.obj" then
    begin
      glEngine#init ();
      glEngine#loadObj "map.obj";
      add_status "Wavefront Loaded, 3D rendering started. Use Arrow Key to move, mouse clic + motion to look around. F1,F2,F3,F4";
      glEngine#initList ();
	id :=
	 match !id with
	     None -> Some (GMain.Timeout.add ~ms:25

			     ~callback:
			     (fun () -> glEngine#display (); true))
	   | Some x -> GMain.Timeout.remove x;
	       Some (GMain.Timeout.add ~ms:25
		       ~callback:
		       (fun () ->
			  glEngine#display (); true))
    end
  else
    begin
      add_status "File map.obj does NOT exists";
      ()
    end

(*string ref which will contain the path to the file we are working on*)
let path = ref ""

(****************************ELEVATION WINDOW****************************)


let sl = ref []

(*Create the structure of the elevation windows (which allow the user to set the
 * altitude values
 * Return a spin box to construct the list of spin box*)
let create_boxies (r,g,b) h bb =
  let h_entry_box = GPack.hbox ~spacing:30 ~packing:bb#add () in
  let spin_box = GEdit.spin_button
               ~adjustment:(GData.adjustment 
                                ~value:h 
                                ~lower:(-9999. )
                                ~upper:10000. 
                                ~page_size:0. ())
               ~digits:2
               ~packing:h_entry_box#pack () in
  let _ = GButton.color_button
                   ~color:(GDraw.color (`RGB (r*255,g*255,b*255)))
                   ~packing:h_entry_box#pack () in
    spin_box

(*Create the spin_box list depending of the number of color on the map *)
let rec create_es_win_rec h bb = function
  |[] -> []
  |((r,g,b),e)::l ->  (create_boxies (r,g,b) (500. -. h) bb)::create_es_win_rec (h -. 25.) bb l

let create_es_win l bb = 
  sl := create_es_win_rec 500. bb l


(*To have the description and the meaning of this function, mail me at
 * hugo.brac@gmail.com *)
let rec add_elev_rec = function
  |(_,[]) | ([],_)-> []
  |((s::l1),((r,g,b),h)::l2) -> ((r,g,b),s#value)::add_elev_rec (l1,l2)

let add_elev sbstep smthStep () =
  ObjCons.heights_list := add_elev_rec (!sl, !ObjCons.heights_list);
  ObjCons.write_obj !path "map.obj" (sbstep#value_as_int) (smthStep#value_as_int);
  add_status "Obj File Created in the Application Directory!";
  ()


(********MAIN BUTTONS AND THEIR FUNCTIONS********)
(*Modifying the Heights_list and create the obj with ObjCons module functions *)
let constr_obj bb () =
 if (!path <> "") then
   begin
     let img = Sdlloader.load_image !path in
     ObjCons.initialize_heights img 0;
     create_es_win !ObjCons.heights_list bb;
     ()
   end
 else
   begin
     add_status "No Image Selected!!";
     ()  
   end

let image2D = new GMisc.image
  (GtkMisc.Image.cast(Glade.get_widget xml "image1"))
let label_image = new GMisc.label
  (GtkMisc.Label.cast(Glade.get_widget xml "label_image"))

(*Set the map with the new image selected*)
let set_map_file () = 
  let pixbuf = GdkPixbuf.from_file_at_size ~height:200 ~width:200 "out.bmp" in
  image2D#set_pixbuf pixbuf;
  let name = split '/' !path in
  label_image#set_text (name.(0)); 
  add_status ("File: " ^ !path);
  ()

let open_windows file_sel () =
  path := "out.bmp";

  add_status "Asking elevation values...";
  (*Elevations selection Window*)
  let es_window = GWindow.window
                  ~title:"Map's Elevations"
                  ~position: `CENTER
		  ~show:false
                  ~resizable:false () in
  let e_megaboxh = GPack.hbox
                ~border_width:10
                ~spacing:10
                ~packing:es_window#add () in
  let e_undermegah = GPack.vbox
                ~packing:e_megaboxh#pack () in
  let pixbuf = GdkPixbuf.from_file_at_size ~height:512 ~width:512 file_sel#filename in
  let preview = GMisc.image ~packing:(e_undermegah#pack) () in
  let lolbox = GPack.hbox
                ~border_width:2
                ~spacing:5
                ~packing:e_undermegah#pack () in
  let label_oil = GMisc.label ~packing:lolbox#pack () in
  let sp_oil = GEdit.spin_button
               ~adjustment:(GData.adjustment 
                                ~value:0. 
                                ~lower:0. 
                                ~upper:5. 
                                ~page_size:0. ())
               ~digits:0
               ~packing:lolbox#pack () in
  let but_oil = GButton.button
		 ~label:"Apply Oilify"
                 ~packing:(lolbox#pack) () in
  let e_bigbox = GPack.vbox
                ~border_width:2
                ~spacing:9
                ~packing:e_megaboxh#pack () in
  let label_step = GMisc.label ~packing:e_bigbox#pack () in
  let spin_box_step = GEdit.spin_button
               ~adjustment:(GData.adjustment 
                                ~value:10. 
                                ~lower:5. 
                                ~upper:100. 
                                ~page_size:0. ())
               ~digits:0
               ~packing:e_bigbox#pack () in
  let label_smooth = GMisc.label ~packing:e_bigbox#pack () in
  let spin_box_smooth = GEdit.spin_button
               ~adjustment:(GData.adjustment 
                                ~value:5. 
                                ~lower:0. 
                                ~upper:100. 
                                ~page_size:0. ())
               ~digits:0
               ~packing:e_bigbox#pack () in
  let label_elev= GMisc.label ~packing:e_bigbox#pack () in
  let scrolled_window = GBin.scrolled_window 
			~border_width:0
		        ~hpolicy:`AUTOMATIC 
			~vpolicy:`AUTOMATIC
			~height:301
			~packing:e_bigbox#pack () in
  let elev_box = GPack.vbox ~spacing:3
                ~packing:scrolled_window#add_with_viewport () in
  let reset = GButton.button
		 ~label:"Set values to 0"
		 ~show:false
                 ~packing:(elev_box#pack) () in
  let label_void = GMisc.label
		 ~text:"Click \"Determine colors \" \nbutton to view the map's \ncolors list"
		 ~show:true
                 ~packing:(elev_box#pack) () in
  let button_box = GPack.hbox
                ~spacing:10
                ~packing:(e_bigbox#pack) () in
  let start_elev_but = GButton.button
		 ~label:"Determine colors"
                 ~packing:(button_box#pack) () in
  let ok_elev_but = GButton.button
                 ~label:"Launch 3D rendering"
		 ~show:false
                 ~packing:(button_box#pack) () in
  let cancel_elev_but = GButton.button
		 ~label:"Cancel"
                 ~packing:(button_box#pack) () in
  label_oil#set_text "You may reduce noise by apply a oilify filter. Factor:";
  label_smooth#set_text "Enter 3D smooth factor:";
  label_step#set_text "Enter Step for 3D sampling:\n(Warning!, small values requires\n a strong computer)";
  label_elev#set_text "Select map's elevations value: \n(Meter)";
  preview#set_pixbuf pixbuf;
  file_sel#misc#hide();
  es_window#show();
  let oilified = ref false in
  oilified := false;
  let rec f = function
    | [] -> ()
    | s::l -> s#set_value 0.; f l in
  ignore(but_oil#connect#clicked ~callback:(fun () ->   oilified := true;
							Ep.calc_edges sp_oil#value_as_int !path file_sel#filename;
							preview#set_pixbuf (GdkPixbuf.from_file_at_size ~height:512 ~width:512 !path);
  							()));
  ignore(reset#connect#clicked ~callback:(fun () -> f !sl; ()));
  ignore(ok_elev_but#connect#clicked ~callback:(fun () ->
							  add_elev spin_box_step spin_box_smooth ();
							  set_map_file ();
							  launch3DEngine();
							  es_window#destroy()));
  ignore(cancel_elev_but#connect#clicked ~callback:(fun () -> add_status"Elevation process canceled.";es_window#destroy ()));
  ignore(start_elev_but#connect#clicked ~callback:(fun () -> 
							start_elev_but#misc#hide();
							if not(!oilified) then Ep.calc_edges 0 !path file_sel#filename;
							label_void#misc#hide ();
							reset#misc#show ();
							ok_elev_but#misc#show();
							constr_obj elev_box ()));
  							()
  

(*File manager function which create a file selector and connect his buttons to
 * the function above ^ *)
let file_mangr () =
  add_status "Choosing a picture...";
  (*File selector*) 
  let file_sel = GWindow.file_selection 
                              ~title:"Select a Map"
                              ~border_width:10 () in
  let _  = file_sel#cancel_button#connect#clicked ~callback:file_sel#destroy in
  let _  = file_sel#ok_button#connect#clicked ~callback:(open_windows file_sel) in
  let _  = file_sel#show () in
    ()

let window = new GWindow.window
   (GtkWindow.Window.cast(Glade.get_widget xml "window1"))
let fichier_ouvrir = new GMenu.image_menu_item
  (GtkMenu.ImageMenuItem.cast(Glade.get_widget xml "ouvrir"))
let fichier_quitter = new GMenu.image_menu_item
  (GtkMenu.ImageMenuItem.cast(Glade.get_widget xml "quitter"))
let eventim = new GBin.event_box
  (GtkBin.EventBox.cast(Glade.get_widget xml "eventbox_image"))
(*view menu button*)
let fillmode_check = new GMenu.check_menu_item
  (GtkMenu.CheckMenuItem.cast(Glade.get_widget xml "Fill Mode"))
(*Check buttons from option frame*)
let grid_check = new GButton.toggle_button
  (GtkButton.ToggleButton.cast(Glade.get_widget xml "grid"))
let psmooth_check = new GButton.toggle_button
  (GtkButton.ToggleButton.cast(Glade.get_widget xml "psmooth"))
let lsmooth_check = new GButton.toggle_button
  (GtkButton.ToggleButton.cast(Glade.get_widget xml "lsmooth"))

let window = new GWindow.window
   (GtkWindow.Window.cast(Glade.get_widget xml "window1"))

(* Warning dialog when you quit the main window *)
let confirm _ =
  let dialog = GWindow.message_dialog
    ~title:"Exit epimapping 3D"
    ~message:
    "<b><big>Exit epimapping 3D?</big></b>\n"
    ~parent:window
    ~destroy_with_parent:true
    ~use_markup:true
    ~message_type:`QUESTION
    ~position:`CENTER_ON_PARENT
    ~buttons:GWindow.Buttons.yes_no () in
  let result = dialog#run () = `NO in
    dialog#destroy ();
    result

let run () =
  ignore (window#event#connect#delete confirm);
  (*Init signal for 3d rendering *)
  let _ = fillmode_check#connect#toggled ~callback:(fun () ->
          glEngine#setFillMode(fillmode_check#active)) in
  let _ = grid_check#connect#toggled ~callback:(fun () ->
          glEngine#setLine(grid_check#active)) in
  let _ = psmooth_check#connect#toggled ~callback:(fun () ->
          glEngine#setGl psmooth_check#active `polygon_smooth ()) in
  let _ = lsmooth_check#connect#toggled ~callback:(fun () ->
          glEngine#setGl lsmooth_check#active `line_smooth ()) in
  let _ = gl_area#connect#reshape ~callback:glEngine#reshape in
  let _ = gl_area#connect#realize ~callback:(fun () -> glEngine#reshape 800 600) in
  let _ = gl_area#connect#display ~callback:glEngine#display in
  let _ = window#event#connect#key_press
          ~callback:(fun ev ->
                       glEngine#kbEvent (GdkEvent.Key.keyval ev);true) in
  let _ = event_boxgl#event#connect#motion_notify
          ~callback:(fun ev ->
                       let x = GdkEvent.Motion.x ev in
                       let y = GdkEvent.Motion.y ev in
                         glEngine#motionEvent x y;
                         true) in
  let _ = eventim#event#connect#button_release
          ~callback:(fun ev -> file_mangr (); true) in
  let _ = fichier_quitter#connect#activate ~callback:destroy in
  let _ = fichier_ouvrir#connect#activate ~callback:file_mangr in
  let _ = window#connect#destroy ~callback:destroy in
  let _ = about#connect#activate ~callback:(fun () -> aboutlol ()) in
  file_mangr ();
  window#show ();
  GMain.Main.main ()
